package rafael.bot;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import roshambo.Player;

public class Rafael implements Player {

    private static final double TRUST_DECAY_FACTOR = 1.1;
    private static final double WEIGHT_UPDATE_FACTOR = 0.5;
    private int choosen;
    private int[] myMoves;
    private int[] itsMoves;
    private int[] advisorsMove;
    private double[] mintrust;
    private double[] w;
    private List<Model> opponentModels;
    private Random random;

    public Rafael(int memory) {
        random = new Random();
        opponentModels = new ArrayList<Model>();
        opponentModels.add(new RandomModel());
        opponentModels.add(new QModel(0.00001, 0, memory));
        opponentModels.add(new PMCModel(memory));
        opponentModels.add(new NaiveBayesModel(memory));
        myMoves = new int[memory];//meu hist�rico de jogadas
        itsMoves = new int[memory];//o hist�rico de jogadas do oponente
    }

    @Override
    public void newGame(int games) {
        for (Model model : opponentModels) {
            model.init(games);
        }
        w = new double[opponentModels.size()];//arrependimento dos conselheiros

        mintrust = new double[opponentModels.size()];//confianca minima nos conselheiros
        advisorsMove = new int[opponentModels.size()];//�ltimo conselho de cada conselheiro

        for (int j = 0; j < opponentModels.size(); j++) {
            w[j] = 1.0;
            mintrust[j] = 1 / (double) opponentModels.size();
        }
    }

    @Override
    public int play() {
        //� preciso fazer todas as previs�es para saber qual dos conselheiros acertou nessa rodada
        for (int j = 0; j < opponentModels.size(); j++) {
            advisorsMove[j] = Model.bestAnswer(
                    opponentModels.get(j).predictMove(
                            VectorUtils.concat(myMoves, itsMoves)));
        }

        choosen = 0;
        //escolhe um conselheiro com probabilidade relativa � taxa de acerto
        double u = random.nextDouble();
        double ac = 0;
        for (int i = 0; i < opponentModels.size(); i++) {
            ac += w[i];
            if (u < ac) {
                choosen = i;
                break;
            }
        }

        return advisorsMove[choosen];
    }

    @Override
    public void receive(int opMove) {
        updateAdvisors(opMove);
        for (int j = 0; j < opponentModels.size(); j++) {
            //tenta aprender o modelo do oponente
            opponentModels.get(j).learn(VectorUtils.concat(myMoves, itsMoves), opMove, 1);
        }
        //atualiza hist�rico
        itsMoves = VectorUtils.appendToHead(itsMoves, opMove);
        myMoves = VectorUtils.appendToHead(myMoves, advisorsMove[choosen]);

    }

    private void updateAdvisors(int opMove) {
        double ci;
        for (int i = 0; i < w.length; i++) {
            ci = c(i, opMove);
            if (i == choosen && ci > 0) {
                mintrust[i] /= TRUST_DECAY_FACTOR;
            }
            if (w[i] > mintrust[i]) {
                w[i] *= Math.pow(WEIGHT_UPDATE_FACTOR, ci);
            }
        }
        w = normalSum(w);
    }

    private double c(int i, int op) {
        if (Model.bestAnswer(advisorsMove[i]) == op) {
            return 2;
        } else if (Model.bestAnswer(op) == advisorsMove[i]) {
            return 0;
        } else {
            return 1;
        }
    }

    private double[] normalSum(double[] v) {
        double[] u = v.clone();
        double size = 0;
        for (int i = 0; i < v.length; i++) {
            size += u[i];
        }
        for (int i = 0; i < u.length; i++) {
            u[i] /= size;
        }
        return u;
    }
}
